﻿using System;
using EnvDTE;
using System.IO;
using System.Text.RegularExpressions;
using System.Collections.Generic;

namespace BuildIncrement {
    static class AutoBuildVersion {
        /// <summary>
        /// Link to the build pane of the output window of Visual Studio.
        /// PS: This instance is set in the OnConnection method of the Connect class
        /// </summary>
        public static OutputWindowPane outputWindowBuildPane;

        /// <summary>
        /// Auto version all solutions present in this solution
        /// </summary>
        public static void AutoVersionSolution() {
            // This gets all solution projects/folders in a easy to use List.
            var solutionItems = new List<SolutionItem>();
            Utility.GetSolutionItems(ref solutionItems);

            // Quite obvious what this function does, isn't it? 
            IterateAndAutoVersionSolutionItems(solutionItems);
        }

        /// <summary>
        /// Iterates through all the subitems of a SolutionItem.
        /// If the item is a project, auto version updater is called
        /// otherwise this function is recursively called for the folder
        /// item.
        /// </summary>
        /// <param name="solutionItems">SolutionItem List</param>
        private static void IterateAndAutoVersionSolutionItems(IEnumerable<SolutionItem> solutionItems) {
            foreach (var si in solutionItems) {
                if (si.ItemType == SolutionItemType.Project) {
                    AutoVersionProject(si.Project);
                }
                else {
                    IterateAndAutoVersionSolutionItems(si.SubItems);
                }
            }
        }

        /// <summary>
        /// Updates assembly version attribute of a project
        /// </summary>
        /// <param name="project">Project to update</param>
        private static void AutoVersionProject(Project project) {
            // Check if auto build versioning is enabled for this project...
            var enabled = Utility.GetGlobalVariable(project, "Enabled", "1") == "1";

            if (enabled) {
                outputWindowBuildPane.OutputString("Versioning " + project.FullName + ": ");

                var assemblyFilePath = "";

                // Find the AssemblyInfo file...
                if (FindAssemblyFile(project, ref assemblyFilePath)) {
                    // If found, set the auto build version...
                    SetApplicationVersion(project, assemblyFilePath);
                }
                else {
                    // ...otherwise...
                    outputWindowBuildPane.OutputString("[ERROR] AssemblyInfo file not found\n");
                }
            }
            else
                outputWindowBuildPane.OutputString("Versioning for " + project.FullName + "is disabled\n");
        }

        /// <summary>
        /// Finds the AssemblyInfo file in a project
        /// </summary>
        /// <param name="project">Project</param>
        /// <param name="assemblyFilePath">String variable to store the path to AssemblyInfo file</param>
        /// <returns>true if file found, false otherwise</returns>
        private static bool FindAssemblyFile(Project project, ref string assemblyFilePath) {
            return ScanForAssembly(project.ProjectItems, ref assemblyFilePath);
        }

        /// <summary>
        /// Finds the AssemblyInfo file in a ProjectItems collection
        /// </summary>
        /// <param name="projectItems">ProjectItems collection to search</param>
        /// <param name="assemblyFilePath">String variable to store the path to AssemblyInfo file</param>
        /// <returns>true if file found, false otherwise</returns>
        private static bool ScanForAssembly(ProjectItems projectItems, ref string assemblyFilePath) {
            foreach (ProjectItem projectItem in projectItems) {
                ProjectItems subItems = projectItem.ProjectItems;

                if (subItems.Count > 0) {
                    // If child ProjectItems collection has entries, it means
                    // we need to search the file in this collection also.
                    // We use recursion for this task...
                    if (ScanForAssembly(subItems, ref assemblyFilePath))
                        return true;
                }
                else {
                    // Found?
                    if (projectItem.Name.Contains("AssemblyInfo")) {
                        // Store the full path...
                        assemblyFilePath = projectItem.get_FileNames(0);

                        // ...and return...
                        return true;
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Sets the assembly version of a project's AssemblyInfo file
        /// </summary>
        /// <param name="project">Project</param>
        /// <param name="assemblyFilePath">Path to AssemblyInfo file</param>
        private static void SetApplicationVersion(Project project, string assemblyFilePath) {
            // Reset AssemblyInfo file's date?
            bool resetFileDate = Utility.GetGlobalVariable(project, "Reset", "1") == "1";

            // Read the file into a string...
            string fileData;

            try {
                TextReader assembly = new StreamReader(assemblyFilePath);
                fileData = assembly.ReadToEnd();
                assembly.Close();
            }
            catch (Exception e) {
                outputWindowBuildPane.OutputString("[ERROR] " + e.Message + "\n");

                return;
            }

            // Last modified time of the assembly to reset...
            var lastModifiedDate = File.GetLastWriteTime(assemblyFilePath);
            // Get project StartDate variable...
            var startDate = Utility.GetProjectDate(project);

            // Find AssemblyVersion attribute...
            var patternAssembly = @"^(\[|<)assembly: AssemblyVersion\(""[0-9]+\.[0-9]+\.(?<Version>[0-9\.\*]+)""\)(>|\])";
            var regexAssembly = new Regex(patternAssembly, RegexOptions.Multiline);
            var matchAssembly = regexAssembly.Match(fileData);

            // Found?
            if (matchAssembly.Success && matchAssembly.Groups["Version"].Success) {
                // Replace the version string...
                var ds = DateTime.Now.DateDifference(startDate);

                fileData = fileData.Remove(matchAssembly.Groups["Version"].Index,
                        matchAssembly.Groups["Version"].Length);
                fileData = fileData.Insert(matchAssembly.Groups["Version"].Index,
                    String.Format("{0}{1:00}.{2:00}{3:00}",
                        (ds.Years * 12) + ds.Months, ds.Days, DateTime.Now.Hour, DateTime.Now.Minute));

                // Save the file back...
                try {
                    TextWriter assemblyWrite = new StreamWriter(assemblyFilePath);
                    assemblyWrite.Write(fileData);
                    assemblyWrite.Close();
                }
                catch (Exception e) {
                    outputWindowBuildPane.OutputString("[ERROR] " + e.Message + "\n");

                    return;
                }

                // ...and reset the file date if set by the user...
                if (resetFileDate) {
                    try {
                        File.SetLastWriteTime(assemblyFilePath, lastModifiedDate);
                    }
                    catch (Exception e) {
                        outputWindowBuildPane.OutputString("[ERROR] " + e.Message + "\n");

                        return;
                    }
                }
            }
            else {
                outputWindowBuildPane.OutputString("[ERROR] AssemblyVersion attribute not found.\n");

                return;
            }

            // Ah!! done...
            outputWindowBuildPane.OutputString("Done\n");
        }
    }
}
